/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.reflection;

import org.apache.ibatis.reflection.invoker.GetFieldInvoker;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.reflection.invoker.MethodInvoker;
import org.apache.ibatis.reflection.property.PropertyTokenizer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

/**
 * MetaClass用于保存指定类的元数据，其中包含了可读、可写的属性、方法以及构造器信息。
 *
 * @author Clinton Begin
 */
public class MetaClass {

    /**
     * Reflector创建工厂
     */
    private final ReflectorFactory reflectorFactory;
    /**
     * 存储指定类的反射数据，简化反射操作
     */
    private final Reflector reflector;

    private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
        this.reflectorFactory = reflectorFactory;
        // 通过反射工厂构造反射描述
        this.reflector = reflectorFactory.findForClass(type);
    }

    /**
     * 为指定的类型生成类型元数据
     *
     * @param type             指定类型
     * @param reflectorFactory 反射工厂
     * @return 类型元数据
     */
    public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
        return new MetaClass(type, reflectorFactory);
    }

    /**
     * 获取指定属性的类型元数据
     *
     * @param name 指定字段
     * @return 指定字段的类型元数据
     */
    public MetaClass metaClassForProperty(String name) {
        // 从反射元数据中获取该名称的getter方法的类型
        Class<?> propType = reflector.getGetterType(name);
        // 生成元数据
        return MetaClass.forClass(propType, reflectorFactory);
    }

    public String findProperty(String name) {
        StringBuilder prop = buildProperty(name, new StringBuilder());
        return prop.length() > 0 ? prop.toString() : null;
    }

    public String findProperty(String name, boolean useCamelCaseMapping) {
        if (useCamelCaseMapping) {
            // 移除下划线
            name = name.replace("_", "");
        }
        return findProperty(name);
    }

    public String[] getGetterNames() {
        return reflector.getGetablePropertyNames();
    }

    public String[] getSetterNames() {
        return reflector.getSetablePropertyNames();
    }

    /**
     * 获取指定名称字段的setter方法的入参类型
     *
     * @param name 字段名称
     * @return 对应的入参类型
     */
    public Class<?> getSetterType(String name) {
        // 迭代属性
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            // 获取对象中该字段的类型元数据
            MetaClass metaProp = metaClassForProperty(prop.getName());
            // 递归解析子属性的类型
            return metaProp.getSetterType(prop.getChildren());
        } else {
            // 获取指定属性setter方法入参的类型
            return reflector.getSetterType(prop.getName());
        }
    }

    public Class<?> getGetterType(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            MetaClass metaProp = metaClassForProperty(prop);
            return metaProp.getGetterType(prop.getChildren());
        }
        // issue #506. Resolve the type inside a Collection Object
        return getGetterType(prop);
    }

    private MetaClass metaClassForProperty(PropertyTokenizer prop) {
        Class<?> propType = getGetterType(prop);
        return MetaClass.forClass(propType, reflectorFactory);
    }

    private Class<?> getGetterType(PropertyTokenizer prop) {
        Class<?> type = reflector.getGetterType(prop.getName());
        if (prop.getIndex() != null && Collection.class.isAssignableFrom(type)) {
            Type returnType = getGenericGetterType(prop.getName());
            if (returnType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
                if (actualTypeArguments != null && actualTypeArguments.length == 1) {
                    returnType = actualTypeArguments[0];
                    if (returnType instanceof Class) {
                        type = (Class<?>) returnType;
                    } else if (returnType instanceof ParameterizedType) {
                        type = (Class<?>) ((ParameterizedType) returnType).getRawType();
                    }
                }
            }
        }
        return type;
    }

    private Type getGenericGetterType(String propertyName) {
        try {
            Invoker invoker = reflector.getGetInvoker(propertyName);
            if (invoker instanceof MethodInvoker) {
                Field _method = MethodInvoker.class.getDeclaredField("method");
                _method.setAccessible(true);
                Method method = (Method) _method.get(invoker);
                return TypeParameterResolver.resolveReturnType(method, reflector.getType());
            } else if (invoker instanceof GetFieldInvoker) {
                Field _field = GetFieldInvoker.class.getDeclaredField("field");
                _field.setAccessible(true);
                Field field = (Field) _field.get(invoker);
                return TypeParameterResolver.resolveFieldType(field, reflector.getType());
            }
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
        return null;
    }

    /**
     * 判断当前Reflector维护的类中是否指定名称的属性的setter方法
     *
     * @param name 属性描述符
     * @return 是否有指定属性的setter方法
     */
    public boolean hasSetter(String name) {
        // 包装属性描述符
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            // 嵌套的属性描述符
            if (reflector.hasSetter(prop.getName())) {
                // 递归生成属性类型的MetaClass对象，并进行setter方法的判断
                MetaClass metaProp = metaClassForProperty(prop.getName());
                return metaProp.hasSetter(prop.getChildren());
            } else {
                // 未包含该属性，返回false
                return false;
            }
        } else {
            // 简单的属性描述符
            // 直接判断是否有指定属性的setter方法
            return reflector.hasSetter(prop.getName());
        }
    }

    /**
     * 判断当前Reflector维护的类中是否指定名称的属性
     *
     * @param name 属性名称
     * @return 当前Reflector维护的类中是否指定名称的属性
     */
    public boolean hasGetter(String name) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            // 嵌套属性的处理
            // 比如：panda.name.first,会判断reflector维护的对象的panda属性的name属性中是否有first属性的getter方法
            if (reflector.hasGetter(prop.getName())) {
                // 为指定属性生成元数据对象，递归完成校验
                MetaClass metaProp = metaClassForProperty(prop);
                return metaProp.hasGetter(prop.getChildren());
            } else {
                // 未包含嵌套属性
                return false;
            }
        } else {
            // 普通属性的处理
            return reflector.hasGetter(prop.getName());
        }
    }

    public Invoker getGetInvoker(String name) {
        return reflector.getGetInvoker(name);
    }

    public Invoker getSetInvoker(String name) {
        return reflector.getSetInvoker(name);
    }

    private StringBuilder buildProperty(String name, StringBuilder builder) {
        PropertyTokenizer prop = new PropertyTokenizer(name);
        if (prop.hasNext()) {
            // 获取指定名称的属性
            String propertyName = reflector.findPropertyName(prop.getName());
            // 属性不存在，跳出递归

            if (propertyName != null) {
                // 属性存在
                builder.append(propertyName);
                builder.append(".");
                // 获取该属性对应的类型元数据
                MetaClass metaProp = metaClassForProperty(propertyName);
                // 递归解析属性
                metaProp.buildProperty(prop.getChildren(), builder);
            }
        } else {
            // 获取属性名
            String propertyName = reflector.findPropertyName(name);
            if (propertyName != null) {
                builder.append(propertyName);
            }
        }
        return builder;
    }

    public boolean hasDefaultConstructor() {
        return reflector.hasDefaultConstructor();
    }

}
